Cleaning up, interpolation, and gridding of weather data for agro-meteorological management purposes

The Nicaraguan case of the ‘Proyecto de Cosecha de Agua (PCA)’

Background

Este bookdown tiene por objetivo documentar la metodologia para la limpieza y analisis de datos de estaciones meteorologicas automaticas (EMAs), asi como tambien, para el tratamiento de datos faltantes y grillado de estos datos provenientes de multiples EMAs. Aqui, se describe las funciones desarrolladas en R, como usarlas y que obtener con cada una de ellas. Posteriormente, estas son aplicadas a un conjunto de datos provenientes del Proyecto de Cosecha de Agua (PCA) que consiste de datos de temperaturas (maxima y minima), humedad relativa, viento (velocidad y direccion), precipitacion y radion solar. Este proyecto se ha llevado a cabo en Nicaragua y cuenta con 8 EMAs distribuidos dentro del area de incidencia del proyecto (Figura 1). Todos lo scripts y datos pueden ser descargados desde el siguiente enlace github: https://github.com/jninanya/PCA. El control de calidad de datos se realizó siguiendo la metodología descrita por el Servicio Nacional de Meteorología e Hidrología (SENAMHI) cuyo manual técnico puede ser descargado aquí.

Figura 1: Estaciones meteorologicas del Proyecto Cosecha de Agua.
Figura 1: Estaciones meteorologicas del Proyecto Cosecha de Agua.

1 Datos y R-funciones

Para fines de esta demostración los datos consisten de un archivo XLSX ("DATOS ESTACIONES-PCA-NICARAGUA.XLSX") que contiene datos meteorlogicos cada 30 minutos de 8 estaciones meteorológicas. Asimismo, se incluye las siguientes funciones en R:

  • XLSXweather_to_csv: Separa el archivo XLSX en archivos individuales CSV para cada estación. Asimismo, selecciona las variables meteorológicas de interes (temperaturas, humedad relativa, precipitación, y radiación).
  • hourly_summary_wdata: Resume los datos por hora y verifica si el registro esta completo o no (check_datetime = TRUE).
  • estimate_thresholds: Estima los umbrales inferior (p01 – percentil 1%) y superior (p99 – percentil 99%) para definir el rango de valores a partir del cual los datos pueden ser sospechosos, de acuerdo con la metodologia del SENAMHI.
  • check_weather_data: Realiza un control de calidad de los datos siguiendo la metodologia del SENAMHI. De momento incluye las pruebas de límites duros y blandos. Se implementará las pruebas de consistencia temporal. El resultado es un archivo CSV que te indica si hay o no datos “raros” y cuáles son.
  • fill_wdata: Completa datos faltantes usando el valor medio (method = "mean"). Se planea implementar una rutina para aplicar algún método de machine leaning para completar datos faltantes (ver seccion XX).

Corramos el siguiente chunk para cargar las funciones:

f1 <- "https://raw.githubusercontent.com/jninanya/PCA/main/R-functions/XLSXweather_to_csv.R"
f2 <- "https://raw.githubusercontent.com/jninanya/PCA/main/R-functions/hourly_summary_wdata.R"
f3 <- "https://raw.githubusercontent.com/jninanya/PCA/main/R-functions/estimate_thresholds.R"
f4 <- "https://raw.githubusercontent.com/jninanya/PCA/main/R-functions/check_weather_data.R"
f5 <- "https://raw.githubusercontent.com/jninanya/PCA/main/R-functions/fill_wdata.R"
f6 <- "https://raw.githubusercontent.com/jninanya/PCA/main/R-functions/QualityControlData.R"

source(url(f1))
source(url(f2))
source(url(f3))
source(url(f4))
source(url(f5))
source(url(f6))

Note que estamos trabajando el la carpeta Desktop/PCA/R-Project dentro del cual hay una carpeta (/R-scripts) conteniendo todas las funciones. Asimismo hay otra carpeta (/Data) que contiene el archivo XLSX con los datos de las estaciones meteorológicas. Adicionalmente vamos a necesitar las siguientes librerias:

#install.packages(c("readxl", "lubridate", "dplyr", "openair"))

library(readxl)      # lectura de archivos XLSX
library(lubridate)   # manejo de fechas
library(dplyr)       # manejo de data frames
library(openair)     # generación rosa de viento

2 Análisis meteorológico: Estación Casa Blanca

2.1 Lectura de datos

Importaremos los datos del archivo XLSX usando la funcion XLSXweather_to_csv. Si queremos trabajar con los datos de todas las estaciones usamos el argumento sheet = "ALL". Para fines práctico, solo trabajaremos con los datos de la estación Casa Blanca por lo que usaremos el argumento sheet = "casa blanca". Para ello, corramos el siguiente chunk:

xlsx_file <- "../../Data/DATOS ESTACIONES-PCA-NICARAGUA.xlsx"

#res <- XLSXweather_to_csv(xlsx_file, sheet = "ALL")
res <- XLSXweather_to_csv(xlsx_file, sheet = "casa blanca")

head(res$casa_blanca)
##         date  time temp tmax tmin rhum wvel wdir prec srad etpo
## 1 2022-01-01 00:30 18.2 18.6 18.2   90    0  ---    0    0    0
## 2 2022-01-01 01:00 17.9 18.2 17.9   92    0  ---    0    0    0
## 3 2022-01-01 01:30 17.7 17.9 17.7   92    0  ---    0    0    0
## 4 2022-01-01 02:00 17.3 17.7 17.3   93    0  ---    0    0    0
## 5 2022-01-01 02:30 16.8 17.3 16.8   92    0  ---    0    0    0
## 6 2022-01-01 03:00 16.7 16.8 16.7   93    0  ---    0    0    0

Las variables que se estan exportando son: temperatura promedio ($temp), temperatura maxima ($tmax). temperatura minima ($tmin), humedad relativa ($rhum), velocidad del viento ($wvel), direccion del viento ($wdir), precipitacion ($prec), y radiacion solar ($srad).

2.2 Resumen horario

La funcion hourly_summary_wdata nos permite obtener el promedio horario de los datos, independientemente del intervalo de registro (30 min en este caso). El argumento check_datetime = TRUE verifica si hay o no datos faltantes. Veamos esto con el siguiente chunk:

weather <- res$casa_blanca
wd <- hourly_summary_wdata(weather, check_datetime = TRUE)

head(wd$hourly)
##              datetime missing  temp  tmax  tmin rhum wvel wdir prec srad etpo
## 1 2022-01-01 00:00:00   FALSE 18.20 18.60 18.20 90.0    0  ---    0    0    0
## 2 2022-01-01 01:00:00   FALSE 17.80 18.05 17.80 92.0    0  ---    0    0    0
## 3 2022-01-01 02:00:00   FALSE 17.05 17.50 17.05 92.5    0  ---    0    0    0
## 4 2022-01-01 03:00:00   FALSE 16.65 16.80 16.65 93.0    0  ---    0    0    0
## 5 2022-01-01 04:00:00   FALSE 16.70 16.75 16.55 93.5    0  ---    0    0    0
## 6 2022-01-01 05:00:00   FALSE 17.00 17.00 16.85 94.0    0  ---    0    0    0

Note que ahora los datos estan resumidos de manera horaria. En el caso de prec y etpo los datos son acumulados, mientras que para wdir se toma la moda ya que es una variable categorica. Asimismo, note que aparece una columna llamada missing el cual indica la presencia de datos faltantes. Veamos cuando missing es TRUE:

head(wd$hourly[wd$hourly$missing == TRUE, ])
##                 datetime missing temp tmax tmin rhum wvel wdir prec srad etpo
## 6941 2022-02-16 14:00:00    TRUE   NA   NA   NA   NA   NA <NA>   NA   NA   NA
## 6942 2022-03-04 13:00:00    TRUE   NA   NA   NA   NA   NA <NA>   NA   NA   NA
## 6943 2022-03-04 14:00:00    TRUE   NA   NA   NA   NA   NA <NA>   NA   NA   NA
## 6944 2022-03-22 03:00:00    TRUE   NA   NA   NA   NA   NA <NA>   NA   NA   NA
## 6945 2022-03-22 04:00:00    TRUE   NA   NA   NA   NA   NA <NA>   NA   NA   NA
## 6946 2022-03-22 05:00:00    TRUE   NA   NA   NA   NA   NA <NA>   NA   NA   NA

Esta funcion tambien retorna como salida la cantidad de valores faltantes por variable (missing_wd$n y missing_wd$percentage). Veamos con el siguiente chunk:

wd$missing_wd
##            n percentage
## datetime 320   4.407713
## temp     326   4.490358
## tmax     326   4.490358
## tmin     326   4.490358
## rhum     326   4.490358
## wvel     320   4.407713
## wdir     320   4.407713
## prec     320   4.407713
## srad     326   4.490358
## etpo     320   4.407713

Note que del total de datos (N = 7260) aproximadamente el 4.4% (n = 323) son datos faltantes. Acontinuacion se determina limites superior e inferior para las variables meteorlogicas definidas en el argumento wd_var.

weather <- wd$hourly
loc <- names(res)[1]
wd_var = c("temp", "tmax", "tmin", "rhum", "wvel", "srad")

par(mfrow = c(2, 3))
tsl = estimate_thresholds(weather, loc, graph = TRUE, wd_var)

LS0tDQp0aXRsZTogIkNsZWFuaW5nIHVwLCBpbnRlcnBvbGF0aW9uLCBhbmQgZ3JpZGRpbmcgb2Ygd2VhdGhlciBkYXRhIGZvciBhZ3JvLW1ldGVvcm9sb2dpY2FsIG1hbmFnZW1lbnQgcHVycG9zZXMiDQpzdWJ0aXRsZTogIlRoZSBOaWNhcmFndWFuIGNhc2Ugb2YgdGhlICdQcm95ZWN0byBkZSBDb3NlY2hhIGRlIEFndWEgKFBDQSknIg0KYXV0aG9yOiAiSm9oYW4gTmluYW55YSAoTjBOMSkiDQpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiDQpzaXRlOiBib29rZG93bjo6Ym9va2Rvd25fc2l0ZQ0KZG9jdW1lbnRjbGFzczogYm9vaw0Kb3V0cHV0Og0KICBybWRmb3JtYXRzOjpyZWFkdGhlZG93bjoNCiAgICBoaWdobGlnaHQ6IGthdGUNCiAgICBudW1iZXJfc2VjdGlvbnM6IFRSVUUNCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFDQotLS0NCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UpDQpgYGANCg0KLSAqKkpvaGFuIE5pbmFueWEsKiogSW50ZXJuYXRpb25hbCBQb3RhdG8gQ2VudGVyIChDSVApDQotICoqTsOpc3RvciBMw7NwZXosKiogQ2VudHJvIEFncm9uw7NtaWNvIFRyb3BpY2FsIGRlIEludmVzdGlnYWNpw7NuIHkgRW5zZcOxYW56YSAoQ0FUSUUpDQotICoqR2FicmllbGEgQ2hhdmVzLCoqIENlbnRybyBBZ3JvbsOzbWljbyBUcm9waWNhbCBkZSBJbnZlc3RpZ2FjacOzbiB5IEVuc2XDsWFuemEgKENBVElFKQ0KLSAqKlJvYmVydG8gUXVpcm96LCoqIENlbnRybyBBZ3JvbsOzbWljbyBUcm9waWNhbCBkZSBJbnZlc3RpZ2FjacOzbiB5IEVuc2XDsWFuemEgKENBVElFKQ0KDQojIEJhY2tncm91bmQgey19DQoNCkVzdGUgYm9va2Rvd24gdGllbmUgcG9yIG9iamV0aXZvIGRvY3VtZW50YXIgbGEgbWV0b2RvbG9naWEgcGFyYSBsYSBsaW1waWV6YSB5IGFuYWxpc2lzIGRlIGRhdG9zIGRlIGVzdGFjaW9uZXMgbWV0ZW9yb2xvZ2ljYXMgYXV0b21hdGljYXMgKEVNQXMpLCBhc2kgY29tbyB0YW1iaWVuLCBwYXJhIGVsIHRyYXRhbWllbnRvIGRlIGRhdG9zIGZhbHRhbnRlcyB5IGdyaWxsYWRvIGRlIGVzdG9zIGRhdG9zIHByb3ZlbmllbnRlcyBkZSBtdWx0aXBsZXMgRU1Bcy4gQXF1aSwgc2UgZGVzY3JpYmUgbGFzIGZ1bmNpb25lcyBkZXNhcnJvbGxhZGFzIGVuIFIsIGNvbW8gdXNhcmxhcyB5IHF1ZSBvYnRlbmVyIGNvbiBjYWRhIHVuYSBkZSBlbGxhcy4gUG9zdGVyaW9ybWVudGUsIGVzdGFzIHNvbiBhcGxpY2FkYXMgYSB1biBjb25qdW50byBkZSBkYXRvcyBwcm92ZW5pZW50ZXMgZGVsICoqUHJveWVjdG8gZGUgQ29zZWNoYSBkZSBBZ3VhIChQQ0EpKiogcXVlIGNvbnNpc3RlIGRlIGRhdG9zIGRlIHRlbXBlcmF0dXJhcyAobWF4aW1hIHkgbWluaW1hKSwgaHVtZWRhZCByZWxhdGl2YSwgdmllbnRvICh2ZWxvY2lkYWQgeSBkaXJlY2Npb24pLCBwcmVjaXBpdGFjaW9uIHkgcmFkaW9uIHNvbGFyLiBFc3RlIHByb3llY3RvIHNlIGhhIGxsZXZhZG8gYSBjYWJvIGVuIE5pY2FyYWd1YSB5IGN1ZW50YSBjb24gOCBFTUFzIGRpc3RyaWJ1aWRvcyBkZW50cm8gZGVsIGFyZWEgZGUgaW5jaWRlbmNpYSBkZWwgcHJveWVjdG8gKEZpZ3VyYSBbMV0oI2ZpZ3VyZTAxKSkuIFRvZG9zIGxvIHNjcmlwdHMgeSBkYXRvcyBwdWVkZW4gc2VyIGRlc2NhcmdhZG9zIGRlc2RlIGVsIHNpZ3VpZW50ZSBlbmxhY2UgZ2l0aHViOiA8aHR0cHM6Ly9naXRodWIuY29tL2puaW5hbnlhL1BDQT4uIEVsIGNvbnRyb2wgZGUgY2FsaWRhZCBkZSBkYXRvcyBzZSByZWFsaXrDsyBzaWd1aWVuZG8gbGEgbWV0b2RvbG9nw61hIGRlc2NyaXRhIHBvciBlbCBTZXJ2aWNpbyBOYWNpb25hbCBkZSBNZXRlb3JvbG9nw61hIGUgSGlkcm9sb2fDrWEgKFNFTkFNSEkpIGN1eW8gbWFudWFsIHTDqWNuaWNvIHB1ZWRlIHNlciBkZXNjYXJnYWRvIFthcXXDrV0oaHR0cHM6Ly93d3cuc2VuYW1oaS5nb2IucGUvbG9hZC9maWxlLzAwNzExU0VOQS01NC5wZGYpLg0KDQo8IS0tIA0KRm9yIGFueSByZXF1ZXN0IGFib3V0IHRoZSBkYXRhc2V0IG9yIG91dHB1dC9wcm9kdWN0IGdlbmVyYXRlZCBhbmQgYW55IGNvbW1lbnQgYWJvdXQgdGhpcyBib29rZG93biwgcGxlYXNlIGNvbnRhY3QgREEgUmFtw61yZXogKCoqZC5yYW1pcmV6QGNnaWFyLm9yZyoqKS4NCi0tPg0KDQoNCjwhLS0gRmlndXJlIDEgLS0+DQo8YSBpZD0iZmlndXJlMDEiPjwvYT4NCjxkaXYgc3R5bGU9InRleHQtYWxpZ246Y2VudGVyOyI+DQogICFbKipGaWd1cmEgMToqKiBFc3RhY2lvbmVzIG1ldGVvcm9sb2dpY2FzIGRlbCBQcm95ZWN0byBDb3NlY2hhIGRlIEFndWEuXShodHRwczovL2dpdGh1Yi5jb20vam5pbmFueWEvUENBL2Jsb2IvbWFpbi9SLWJvb2tkb3duL0ZpZ3VyZXMvNi1Fc3RhY2lvbmVzJTIwbWV0ZW9yb2wlQzMlQjNnaWNhcy5qcGc/cmF3PXRydWUpe3dpZHRoPTc1JX0NCjwvZGl2Pg0KDQoNCg0KDQo8IS0tY2hhcHRlcjplbmQ6aW5kZXguUm1kLS0+DQoNCiMgRGF0b3MgeSBSLWZ1bmNpb25lcw0KUGFyYSBmaW5lcyBkZSBlc3RhIGRlbW9zdHJhY2nDs24gbG9zIGRhdG9zIGNvbnNpc3RlbiBkZSB1biBhcmNoaXZvIFhMU1ggKGAiREFUT1MgRVNUQUNJT05FUy1QQ0EtTklDQVJBR1VBLlhMU1giYCkgcXVlIGNvbnRpZW5lIGRhdG9zIG1ldGVvcmxvZ2ljb3MgY2FkYSAzMCBtaW51dG9zIGRlIDggZXN0YWNpb25lcyBtZXRlb3JvbMOzZ2ljYXMuIEFzaW1pc21vLCBzZSBpbmNsdXllIGxhcyBzaWd1aWVudGVzIGZ1bmNpb25lcyBlbiBSOg0KDQoqIGBYTFNYd2VhdGhlcl90b19jc3ZgOiBTZXBhcmEgZWwgYXJjaGl2byBYTFNYIGVuIGFyY2hpdm9zIGluZGl2aWR1YWxlcyBDU1YgcGFyYSBjYWRhIGVzdGFjacOzbi4gQXNpbWlzbW8sIHNlbGVjY2lvbmEgbGFzIHZhcmlhYmxlcyBtZXRlb3JvbMOzZ2ljYXMgZGUgaW50ZXJlcyAodGVtcGVyYXR1cmFzLCBodW1lZGFkIHJlbGF0aXZhLCBwcmVjaXBpdGFjacOzbiwgeSByYWRpYWNpw7NuKS4gDQoqIGBob3VybHlfc3VtbWFyeV93ZGF0YWA6IFJlc3VtZSBsb3MgZGF0b3MgcG9yIGhvcmEgeSB2ZXJpZmljYSBzaSBlbCByZWdpc3RybyBlc3RhIGNvbXBsZXRvIG8gbm8gKGBjaGVja19kYXRldGltZSA9IFRSVUVgKS4NCiogYGVzdGltYXRlX3RocmVzaG9sZHNgOiBFc3RpbWEgbG9zIHVtYnJhbGVzIGluZmVyaW9yICgqKnAwMSoqIC0tIHBlcmNlbnRpbCAxJSkgeSBzdXBlcmlvciAoKipwOTkqKiAtLSBwZXJjZW50aWwgOTklKSBwYXJhIGRlZmluaXIgZWwgcmFuZ28gZGUgdmFsb3JlcyBhIHBhcnRpciBkZWwgY3VhbCBsb3MgZGF0b3MgcHVlZGVuIHNlciBzb3NwZWNob3NvcywgZGUgYWN1ZXJkbyBjb24gbGEgbWV0b2RvbG9naWEgZGVsIFtTRU5BTUhJXShodHRwczovL3JlcG9zaXRvcmlvLnNlbmFtaGkuZ29iLnBlL2hhbmRsZS8yMC41MDAuMTI1NDIvNDQ5KS4gIA0KKiBgY2hlY2tfd2VhdGhlcl9kYXRhYDogUmVhbGl6YSB1biBjb250cm9sIGRlIGNhbGlkYWQgZGUgbG9zIGRhdG9zIHNpZ3VpZW5kbyBsYSBtZXRvZG9sb2dpYSBkZWwgW1NFTkFNSEldKGh0dHBzOi8vd3d3LnNlbmFtaGkuZ29iLnBlL2xvYWQvZmlsZS8wMDcxMVNFTkEtNTQucGRmKS4gRGUgbW9tZW50byBpbmNsdXllIGxhcyBwcnVlYmFzIGRlIGzDrW1pdGVzIGR1cm9zIHkgYmxhbmRvcy4gU2UgaW1wbGVtZW50YXLDoSBsYXMgcHJ1ZWJhcyBkZSBjb25zaXN0ZW5jaWEgdGVtcG9yYWwuIEVsIHJlc3VsdGFkbyBlcyB1biBhcmNoaXZvIENTViBxdWUgdGUgaW5kaWNhIHNpIGhheSBvIG5vIGRhdG9zICJyYXJvcyIgeSBjdcOhbGVzIHNvbi4gDQoqIGBmaWxsX3dkYXRhYDogQ29tcGxldGEgZGF0b3MgZmFsdGFudGVzIHVzYW5kbyBlbCB2YWxvciBtZWRpbyAoYG1ldGhvZCA9ICJtZWFuImApLiBTZSBwbGFuZWEgaW1wbGVtZW50YXIgdW5hIHJ1dGluYSBwYXJhIGFwbGljYXIgYWxnw7puIG3DqXRvZG8gZGUgbWFjaGluZSBsZWFuaW5nIHBhcmEgY29tcGxldGFyIGRhdG9zIGZhbHRhbnRlcyAodmVyIHNlY2Npb24gWFgpLg0KDQpDb3JyYW1vcyBlbCBzaWd1aWVudGUgY2h1bmsgcGFyYSBjYXJnYXIgbGFzIGZ1bmNpb25lczogDQoNCmBgYHtyfQ0KZjEgPC0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9qbmluYW55YS9QQ0EvbWFpbi9SLWZ1bmN0aW9ucy9YTFNYd2VhdGhlcl90b19jc3YuUiINCmYyIDwtICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vam5pbmFueWEvUENBL21haW4vUi1mdW5jdGlvbnMvaG91cmx5X3N1bW1hcnlfd2RhdGEuUiINCmYzIDwtICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vam5pbmFueWEvUENBL21haW4vUi1mdW5jdGlvbnMvZXN0aW1hdGVfdGhyZXNob2xkcy5SIg0KZjQgPC0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9qbmluYW55YS9QQ0EvbWFpbi9SLWZ1bmN0aW9ucy9jaGVja193ZWF0aGVyX2RhdGEuUiINCmY1IDwtICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vam5pbmFueWEvUENBL21haW4vUi1mdW5jdGlvbnMvZmlsbF93ZGF0YS5SIg0KZjYgPC0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9qbmluYW55YS9QQ0EvbWFpbi9SLWZ1bmN0aW9ucy9RdWFsaXR5Q29udHJvbERhdGEuUiINCg0Kc291cmNlKHVybChmMSkpDQpzb3VyY2UodXJsKGYyKSkNCnNvdXJjZSh1cmwoZjMpKQ0Kc291cmNlKHVybChmNCkpDQpzb3VyY2UodXJsKGY1KSkNCnNvdXJjZSh1cmwoZjYpKQ0KYGBgDQoNCk5vdGUgcXVlIGVzdGFtb3MgdHJhYmFqYW5kbyBlbCBsYSBjYXJwZXRhICoqRGVza3RvcC9QQ0EvUi1Qcm9qZWN0KiogZGVudHJvIGRlbCBjdWFsIGhheSB1bmEgY2FycGV0YSAoKiovUi1zY3JpcHRzKiopIGNvbnRlbmllbmRvIHRvZGFzIGxhcyBmdW5jaW9uZXMuIEFzaW1pc21vIGhheSBvdHJhIGNhcnBldGEgKCoqL0RhdGEqKikgcXVlIGNvbnRpZW5lIGVsIGFyY2hpdm8gWExTWCBjb24gbG9zIGRhdG9zIGRlIGxhcyBlc3RhY2lvbmVzIG1ldGVvcm9sw7NnaWNhcy4gQWRpY2lvbmFsbWVudGUgdmFtb3MgYSBuZWNlc2l0YXIgbGFzIHNpZ3VpZW50ZXMgbGlicmVyaWFzOiAgIA0KDQpgYGB7cn0NCiNpbnN0YWxsLnBhY2thZ2VzKGMoInJlYWR4bCIsICJsdWJyaWRhdGUiLCAiZHBseXIiLCAib3BlbmFpciIpKQ0KDQpsaWJyYXJ5KHJlYWR4bCkgICAgICAjIGxlY3R1cmEgZGUgYXJjaGl2b3MgWExTWA0KbGlicmFyeShsdWJyaWRhdGUpICAgIyBtYW5lam8gZGUgZmVjaGFzDQpsaWJyYXJ5KGRwbHlyKSAgICAgICAjIG1hbmVqbyBkZSBkYXRhIGZyYW1lcw0KbGlicmFyeShvcGVuYWlyKSAgICAgIyBnZW5lcmFjacOzbiByb3NhIGRlIHZpZW50bw0KYGBgDQoNCiMgQW7DoWxpc2lzIG1ldGVvcm9sw7NnaWNvOiBFc3RhY2nDs24gQ2FzYSBCbGFuY2ENCg0KIyMgTGVjdHVyYSBkZSBkYXRvcw0KSW1wb3J0YXJlbW9zIGxvcyBkYXRvcyBkZWwgYXJjaGl2byBYTFNYIHVzYW5kbyBsYSBmdW5jaW9uIGBYTFNYd2VhdGhlcl90b19jc3ZgLiBTaSBxdWVyZW1vcyB0cmFiYWphciBjb24gbG9zIGRhdG9zIGRlIHRvZGFzIGxhcyBlc3RhY2lvbmVzIHVzYW1vcyBlbCBhcmd1bWVudG8gYHNoZWV0ID0gIkFMTCJgLiBQYXJhIGZpbmVzIHByw6FjdGljbywgc29sbyB0cmFiYWphcmVtb3MgY29uIGxvcyBkYXRvcyBkZSBsYSBlc3RhY2nDs24gKipDYXNhIEJsYW5jYSoqIHBvciBsbyBxdWUgdXNhcmVtb3MgZWwgYXJndW1lbnRvIGBzaGVldCA9ICJjYXNhIGJsYW5jYSJgLiBQYXJhIGVsbG8sIGNvcnJhbW9zIGVsIHNpZ3VpZW50ZSBjaHVuazoNCg0KYGBge3J9DQp4bHN4X2ZpbGUgPC0gIi4uLy4uL0RhdGEvREFUT1MgRVNUQUNJT05FUy1QQ0EtTklDQVJBR1VBLnhsc3giDQoNCiNyZXMgPC0gWExTWHdlYXRoZXJfdG9fY3N2KHhsc3hfZmlsZSwgc2hlZXQgPSAiQUxMIikNCnJlcyA8LSBYTFNYd2VhdGhlcl90b19jc3YoeGxzeF9maWxlLCBzaGVldCA9ICJjYXNhIGJsYW5jYSIpDQoNCmhlYWQocmVzJGNhc2FfYmxhbmNhKQ0KDQpgYGANCg0KTGFzIHZhcmlhYmxlcyBxdWUgc2UgZXN0YW4gZXhwb3J0YW5kbyBzb246IHRlbXBlcmF0dXJhIHByb21lZGlvIChgJHRlbXBgKSwgdGVtcGVyYXR1cmEgbWF4aW1hIChgJHRtYXhgKS4gdGVtcGVyYXR1cmEgbWluaW1hIChgJHRtaW5gKSwgaHVtZWRhZCByZWxhdGl2YSAoYCRyaHVtYCksIHZlbG9jaWRhZCBkZWwgdmllbnRvIChgJHd2ZWxgKSwgZGlyZWNjaW9uIGRlbCB2aWVudG8gKGAkd2RpcmApLCBwcmVjaXBpdGFjaW9uIChgJHByZWNgKSwgeSByYWRpYWNpb24gc29sYXIgKGAkc3JhZGApLg0KDQojIyBSZXN1bWVuIGhvcmFyaW8NCg0KTGEgZnVuY2lvbiBgaG91cmx5X3N1bW1hcnlfd2RhdGFgIG5vcyBwZXJtaXRlIG9idGVuZXIgZWwgcHJvbWVkaW8gaG9yYXJpbyBkZSBsb3MgZGF0b3MsIGluZGVwZW5kaWVudGVtZW50ZSBkZWwgaW50ZXJ2YWxvIGRlIHJlZ2lzdHJvICgzMCBtaW4gZW4gZXN0ZSBjYXNvKS4gRWwgYXJndW1lbnRvIGBjaGVja19kYXRldGltZSA9IFRSVUVgIHZlcmlmaWNhIHNpIGhheSBvIG5vIGRhdG9zIGZhbHRhbnRlcy4gVmVhbW9zIGVzdG8gY29uIGVsIHNpZ3VpZW50ZSBjaHVuazogDQoNCmBgYHtyfQ0Kd2VhdGhlciA8LSByZXMkY2FzYV9ibGFuY2ENCndkIDwtIGhvdXJseV9zdW1tYXJ5X3dkYXRhKHdlYXRoZXIsIGNoZWNrX2RhdGV0aW1lID0gVFJVRSkNCg0KaGVhZCh3ZCRob3VybHkpDQpgYGANCg0KTm90ZSBxdWUgYWhvcmEgbG9zIGRhdG9zIGVzdGFuIHJlc3VtaWRvcyBkZSBtYW5lcmEgaG9yYXJpYS4gRW4gZWwgY2FzbyBkZSBgcHJlY2AgeSBgZXRwb2AgbG9zIGRhdG9zIHNvbiAqYWN1bXVsYWRvcyosIG1pZW50cmFzIHF1ZSBwYXJhIGB3ZGlyYCBzZSB0b21hIGxhICptb2RhKiB5YSBxdWUgZXMgdW5hIHZhcmlhYmxlIGNhdGVnb3JpY2EuIEFzaW1pc21vLCBub3RlIHF1ZSBhcGFyZWNlIHVuYSBjb2x1bW5hIGxsYW1hZGEgYG1pc3NpbmdgIGVsIGN1YWwgaW5kaWNhIGxhIHByZXNlbmNpYSBkZSBkYXRvcyBmYWx0YW50ZXMuIFZlYW1vcyBjdWFuZG8gYG1pc3NpbmdgIGVzIGBUUlVFYDogDQoNCmBgYHtyfQ0KaGVhZCh3ZCRob3VybHlbd2QkaG91cmx5JG1pc3NpbmcgPT0gVFJVRSwgXSkNCmBgYA0KDQpFc3RhIGZ1bmNpb24gdGFtYmllbiByZXRvcm5hIGNvbW8gc2FsaWRhIGxhIGNhbnRpZGFkIGRlIHZhbG9yZXMgZmFsdGFudGVzIHBvciB2YXJpYWJsZSAoYG1pc3Npbmdfd2QkbmAgeSBgbWlzc2luZ193ZCRwZXJjZW50YWdlYCkuIFZlYW1vcyBjb24gZWwgc2lndWllbnRlIGNodW5rOg0KDQpgYGB7cn0NCndkJG1pc3Npbmdfd2QNCmBgYA0KDQpOb3RlIHF1ZSBkZWwgdG90YWwgZGUgZGF0b3MgKE4gPSBgciBucm93KHdkJGhvdXJseSlgKSBhcHJveGltYWRhbWVudGUgZWwgYHIgcm91bmQobWVhbih3ZCRtaXNzaW5nX3dkWywyXSksIDEpYCUgKG4gPSBgciByb3VuZChtZWFuKHdkJG1pc3Npbmdfd2RbLDFdKSwgMClgKSBzb24gZGF0b3MgZmFsdGFudGVzLiBBY29udGludWFjaW9uIHNlIGRldGVybWluYSBsaW1pdGVzIHN1cGVyaW9yIGUgaW5mZXJpb3IgcGFyYSBsYXMgdmFyaWFibGVzIG1ldGVvcmxvZ2ljYXMgZGVmaW5pZGFzIGVuIGVsIGFyZ3VtZW50byBgd2RfdmFyYC4NCg0KYGBge3J9DQp3ZWF0aGVyIDwtIHdkJGhvdXJseQ0KbG9jIDwtIG5hbWVzKHJlcylbMV0NCndkX3ZhciA9IGMoInRlbXAiLCAidG1heCIsICJ0bWluIiwgInJodW0iLCAid3ZlbCIsICJzcmFkIikNCg0KcGFyKG1mcm93ID0gYygyLCAzKSkNCnRzbCA9IGVzdGltYXRlX3RocmVzaG9sZHMod2VhdGhlciwgbG9jLCBncmFwaCA9IFRSVUUsIHdkX3ZhcikNCmBgYA0KDQoNCg0KDQoNCg0KDQoNCjwhLS1jaGFwdGVyOmVuZDowMS1DbGVhbmluZy11cC5SbWQtLT4NCg0K